local skynet = require "skynet"
local socket = require "skynet.socket"
local sproto = require "sproto"
local sprotoloader = require "sprotoloader"

local runconfig = require "runconfig"

local libpersistent = require "libpersistent" -- 数据持久化模块
local libroom = require "libroom"
local liblog = require "liblog"

local agent_session_expire = skynet.getenv("agent_session_expire") or 600
agent_session_expire = tonumber(agent_session_expire) -- 用户会话过期时间

local watchdog = ... -- 由watchdog，直接关联上即可
local host
local send_request

local CMD = {}
local REQUEST = {}
local m_client_fd, m_client_fd -- 只作为与客户端通信的fdip

local agent_state = {
	client = {
		ip = nil, 
		fd = nil, 
	},

	userinfo = {

	},

	state = {
		afk = false, 
		last_active = skynet.time(), 
	},

}

--------------------------------------------------------------

function REQUEST:handshake()
	return { msg = "Welcome to skynet, I will send heartbeat every 5 sec." }
end

function REQUEST:afk()
	skynet.call(watchdog, "lua", "afk", agent_state.userinfo.uid)
end 

-- agent->watchdog->gated->[ msgserver, logind ] 
-- but agent not exit, recycle ? -> CMD.logout
function REQUEST:logout()
	if agent_state.state.room then 
		libroom.leave_room(agent_state.state.room, agent_state.userinfo)
	end 

	skynet.call(watchdog, "lua", "logout", agent_state.userinfo.uid)
end 

function REQUEST:enter_room()
	local result
	result = libroom.enter_room(self.id, agent_state.userinfo, skynet.self())
	return { ok = result }
end 

function REQUEST:leave_room()
	local result
	if agent_state.state.room then 
		result = libroom.leave_room(agent_state.state.room, agent_state.userinfo)
	end 
	if result then 
		agent_state.state.room = false -- notify_user_leave也执行false
	end 
	return { ok = result }
end

function REQUEST:list_members()
	local result
	if agent_state.state.room then 
		result = libroom.list_members(agent_state.state.room, agent_state.userinfo)
	end 
	return { members = result }
end 

function REQUEST:list_rooms()
	local mynode = skynet.getenv("node")
	local nodecfg = runconfig[mynode]
	return { rooms = nodecfg.room }
end 

function REQUEST:say_public()
	if agent_state.state.room then 
		local msg = self.msg
		local user_info = agent_state.userinfo
		libroom.say_public(user_info, msg)
	end 
end 

function REQUEST:say_private()
	if agent_state.state.room then 
		local msg = self.msg 
		local to_uid = self.to 
		local user_info = agent_state.userinfo
		libroom.say_private(user_info, to_uid, msg)
	end 
end 

function REQUEST:kick()
	local from_uid = self.from 
	local to_uid = self.to 

	local result = false
	if agent_state.state.room then 
		result = libroom.kick(from_uid, to_uid)
	end 
	return { ok = result }
end 

function REQUEST:send_exp()
	local from_uid = self.from 
	local to_uid = self.to 
	local exp = self.exp 

	local result = false 
	if agent_state.state.room and agent_state.userinfo.exp >= exp and exp > 0 then 
		result = libroom.send_exp(from_uid, to_uid, exp)
	end 
	return { ok = result }
end 

--------------------------------------------------------------

local function keep_alive()
	agent_state.state.last_active = skynet.time()
end 


local function request(name, args, response)
	-- print("request", name, args)
	local f = assert(REQUEST[name])
	local r = f(args)
	if response then
		return response(r)
	end
end

local function send_package(pack)
	local package = string.pack(">s2", pack)
	socket.write(m_client_fd, package) -- m_client_fd: 因为退出指令清空agent_state.client
end

skynet.register_protocol {
	name = "client",
	id = skynet.PTYPE_CLIENT,
	unpack = function (msg, sz) -- gated： netpack.tostring(msg, sz) -- skynet 发原始数据指针
		return host:dispatch(msg, sz)
	end,
	dispatch = function (fd, _, type, ...)
		assert(fd == m_client_fd)	-- You can use fd to reply message
		skynet.ignoreret()	-- session is fd, don't call skynet.ret
		-- skynet.trace()
		if type == "REQUEST" then
			local ok, result  = pcall(request, ...)

			if ok then
				if result then
					send_package(result) 
				end
			else
				logger.error("agent", "error when handle request", result)
				-- skynet.error(result)
			end
		else
			assert(type == "RESPONSE")
		end

		keep_alive()
	end
}



--------------------------------------------------------------



-- call by watchdog -- timer_watchdog
-- 检查空闲状态，超时不动作，则强制下线。
function CMD.check_idle()
	local now = skynet.time() -- 获取当前时间
	local last_active = agent_state.state.last_active -- 获取上一次活跃时间
	
	local gap_time = now - last_active 
	if gap_time >= agent_session_expire then 
		skynet.call(watchdog, "lua", "recycle_agent", agent_state.userinfo.uid, skynet.self())
	end 
end 

-- call by watchdog
function CMD.persistent()
	logger.debug("agent", "uid", agent_state.userinfo.uid, "start to save data")

	libpersistent.save_user_data(agent_state.userinfo.uid, agent_state.userinfo)
end 

-- call by watchdog
function CMD.load_user_data(uid)
	logger.debug("agent", "load data from database for uid", uid)

	uid = assert(tonumber(uid), "uid not a number")
	local user_info = libpersistent.load_user_data(uid)

	if user_info then 
		agent_state.userinfo = user_info
		return true 
	end 

	return false
end 

function CMD.notify_exp_change(exp)
	if agent_state.userinfo.exp then 
		liblog.log_exp_change(agent_state.userinfo.uid, "expchange", exp)

		agent_state.userinfo.exp = agent_state.userinfo.exp + exp
	end 
end 

function CMD.notify_user_enter(room_id, userinfo) 
	if userinfo.uid == agent_state.userinfo.uid then 
		-- 是自己进入，绑定room
		agent_state.state.room = room_id 

		liblog.log_system_activity(agent_state.userinfo.uid, "enterroom", "User enter room " .. agent_state.state.room)
	end 

	local result = {
		uid = userinfo.uid,
		roomid = room_id,
		username = userinfo.username,
	}
	send_package(send_request("enter_room_message", result))
end 	

function CMD.notify_user_leave(room_id, userinfo)
	if userinfo.uid == agent_state.userinfo.uid then 
		agent_state.state.room = false 
	end 
	local result = {
		uid = userinfo.uid,
		roomid = room_id, 
		username = userinfo.username,
	}
	send_package(send_request("leave_room_message", result))
end 

function CMD.notify_talking_message(args)
	local from_uid = args.from_uid
	local to_uid = args.to_uid
	local msg = args.msg 

	local result = {
		from = from_uid,
		to = to_uid,
		msg = msg,
	}

	if to_uid == 0 then 
		send_package(send_request("say_public_message", result))
	elseif to_uid == agent_state.userinfo.uid then 
		send_package(send_request("say_private_message", result))
	end 
end 

function CMD.notify_kick_message(args)
	local from = args.from 
	local to = args.to 
	
	local result = {
		msg = "manager " .. from .. " kick " .. to ..  " out of the room" 
	}

	send_package(send_request("kick_message", result))
end 

function CMD.notify_send_exp_message(args)
	local from = args.from 
	local to = args.to 
	local exp = args.exp 

	local result = {
		msg = "User " .. from .. " send exp " .. exp .. " to User " .. to
	}

	send_package(send_request("send_exp_message", result))
end 

----------------------------------------------------------------------

-- call by watchdog
function CMD.auth_client(fd, ip)
	logger.debug("agent", "auth_client fd", fd, "ip", ip)

	agent_state.client.fd = tonumber(fd) 
	agent_state.client.ip = string.sub(ip, 1, string.find(ip, ":") - 1)
	m_client_fd = agent_state.client.fd
	m_client_ip = agent_state.client.ip

	agent_state.state.afk = false 
end 

-- call by watchdog
function CMD.recycle()
	logger.debug("agent", "agent uid", agent_state.userinfo.uid, "data reset for reuse")

	agent_state.state = { afk = true, last_active = 0 }
	agent_state.client = {}
	agent_state.userinfo = {}
end 

function CMD.afk()
	logger.info("agent", "uid", agent_state.userinfo.uid, "username", agent_state.userinfo.username, "away from keyboard")

	agent_state.client = {} 
	agent_state.state.afk = true 

	CMD.persistent()
end 

-- call by watchdog
--[[
	告诉看门狗，是否允许注销，候代理是afk状态等。
]]
function CMD.logout()
	cached = {}

	agent_state.client = {}
	agent_state.state.afk = true 

	return true;
	-- skynet.exit()
end 

skynet.start(function()
	host = sprotoloader.load(1):host "package"
	send_request = host:attach(sprotoloader.load(2))

	skynet.dispatch("lua", function(_,_, command, ...)
		-- skynet.trace()
		local f = CMD[command]
		skynet.ret(skynet.pack(f(...)))
	end)
end)
